package net.gdface.facedb;

import java.nio.ByteBuffer;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import com.gitee.l0km.com4j.base.BinaryUtils;
import com.gitee.l0km.com4j.base.exception.NotFoundBeanException;
import com.gitee.l0km.ximage.ImageErrorException;
import com.gitee.l0km.ximage.MatType;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;

import gu.sql2java.exception.ObjectRetrievalException;
import net.gdface.facedb.db.FaceBean;
import net.gdface.facedb.db.FeatureBean;
import net.gdface.facedb.db.ImageBean;
import net.gdface.sdk.CodeInfo;
import net.gdface.sdk.CompareResult;
import net.gdface.sdk.NotFaceDetectedException;

import static com.google.common.base.Preconditions.*;

/**
 * 实现{@link FaceDb}本地共用方法的抽象类,不涉及人脸算法,不支持人脸搜索
 * @author guyadong
 *
 */
public class FacedbLocal implements FaceDb,CommonConstant,CapcityFieldConstant {
	protected final Map<String, String> capacity = new HashMap<>();
	private final Map<String, String> roCapacity = Collections.unmodifiableMap(capacity);

	protected final Dao dao = new Dao();

	public FacedbLocal() {
		super();
		capacity.put(C_SUPPORT_SEARCH, Boolean.FALSE.toString());
	}

	protected void checkMulti() {
		throw new UnsupportedOperationException();
	}

	protected void checkNotMulti() {
		throw new UnsupportedOperationException();
	}
	private static Date toDate(String date) throws ParseException{
		if(Strings.isNullOrEmpty(date)){
			return null;
		}
		try {
			return new SimpleDateFormat(ISO8601_FORMATTER_STR).parse(date);
		} catch (ParseException e) {
			try {
				return new SimpleDateFormat(TIMESTAMP_FORMATTER_STR).parse(date);
			} catch (ParseException e2) {
				return new SimpleDateFormat(DATE_FORMATTER_STR).parse(date);
			}
		}
	}
	@Override
	public final boolean hasFeature(byte[] feature) {
		return hasFeatureByMD5(BinaryUtils.getMD5String(feature));
	}

	@Override
	public final boolean hasFeatureByMD5(String featureId) {
		return getFeature(featureId) !=null;
	}

	@Override
	public final boolean hasImage(String imageMd5){
		return getImage(imageMd5)!=null;
	}

	@Override
	public final FaceBean getFaceByFeatureId(String featureId) {
		List<FaceBean> faceBeans = getFacesByFeatureId(featureId);
		checkState(faceBeans.size() <= 1,"FEATURE vs FACE table is not 1:1 map");
		return faceBeans.size() ==1 ? faceBeans.get(0) : null;
	}
	@Override
	public final ImageBean getImageByFeatureId(String featureId) {
		FaceBean featureBean = getFaceByFeatureId(featureId);
		return null == featureBean ? null : getImage(featureBean.getImageMd5());
	}
	@Override
	public final FeatureBean getFeatureByImageMd5(String imageMd5) {
		List<FeatureBean> featureBeans = getFeaturesByImageMd5(imageMd5);
		checkState(featureBeans.size() <= 1,"IMAGE vs FEATURE table is not 1:1 map");
		return featureBeans.size() ==1 ? featureBeans.get(0) : null; 
	}
	@Override
	public final FaceBean getFaceByImageMd5(String imageMd5) {
		List<FaceBean> featureBeans = getFacesByImageMd5(imageMd5);
		checkState(featureBeans.size() <= 1,"IMAGE vs FACE table is not 1:1 map");
		return featureBeans.size() ==1 ? featureBeans.get(0) : null; 
	}
	@Override
	public final FeatureBean getFeatureByFaceId(int faceId) {
		FaceBean faceBean = getFace(faceId);
		return null == faceBean ? null : getFeature(faceBean.getFeatureMd5());
	}
	public FeatureBean getFeatureChecked(String featureId) throws NotFoundBeanException {
		try{
			return dao.daoGetFeatureChecked(featureId);
		}catch(ObjectRetrievalException e){
			throw new NotFoundBeanException(String.format("not exist row in FEATURE table with PK:%s", featureId));
		}
	}
	@Override
	public final ImageBean getImageByFaceId(int faceId) {
		FaceBean faceBean = getFace(faceId);
		return null == faceBean ? null : getImage(faceBean.getImageMd5());
	}
	@Override
	public FeatureBean addFeature(final byte[] feature, final Map<ByteBuffer, CodeInfo> faces) throws DuplicateRecordException {
		checkMulti();
		checkNotNull(feature);
		try{
			return BaseDao.daoRunAsTransaction(new Callable<FeatureBean>(){
				@Override
				public FeatureBean call() throws Exception {
					return dao.daoAddFeature(ByteBuffer.wrap(feature), faces);
				}});
		}catch(Exception e){
			Throwables.propagateIfPossible(e.getCause(), DuplicateRecordException.class);
			Throwables.throwIfUnchecked(e);
			throw new RuntimeException(e);
		}
	}
	@Override
	public ImageBean addImage(final byte[] imgData, final List<CodeInfo> features) throws DuplicateRecordException {
		checkNotMulti();
		checkNotNull(imgData);
		try{
			return BaseDao.daoRunAsTransaction(new Callable<ImageBean>(){
				@Override
				public ImageBean call() throws Exception {
					return dao.daoAddImage(ByteBuffer.wrap(imgData),features);
				}});
		}catch(Exception e){
			Throwables.propagateIfPossible(e.getCause(), DuplicateRecordException.class);
			Throwables.throwIfUnchecked(e);
			throw new RuntimeException(e);
		}
	}

	@Override
	public boolean deleteImage(final String imgMd5, final boolean cascade) {
		return BaseDao.daoRunAsTransaction(new Callable<Boolean>(){
			@Override
			public Boolean call() throws Exception {
				return dao.daoDeleteImage(imgMd5, cascade) == 1;
			}});
	}
	@Override
	public int deleteImages(final List<String> imgMd5List, final boolean cascade) {
		return BaseDao.daoRunAsTransaction(new Callable<Integer>(){
			@Override
			public Integer call() throws Exception {
				return dao.daoDeleteImages(imgMd5List,cascade);
			}});
	}

	@Override
	public boolean deleteFeature(final String featureId, final boolean cascade) {
		return BaseDao.daoRunAsTransaction(new Callable<Boolean>(){
			@Override
			public Boolean call() throws Exception {
				return dao.daoDeleteFeature(featureId, cascade) == 1;
			}});
	}
	@Override
	public int deleteFeatures(final List<String> featureIdList, final boolean cascade) {
		return BaseDao.daoRunAsTransaction(new Callable<Integer>(){
			@Override
			public Integer call() throws Exception {
				return dao.daoDeleteFeatures(featureIdList,cascade);
			}});
	}
	@Override
	public FaceBean getFace(int faceId){
		return dao.daoGetFace(faceId);
	}

	private final Function<FaceBean,CodeInfo> transFace2CodeInfo = new Function<FaceBean,CodeInfo>(){
		@Override
		public CodeInfo apply(FaceBean input) {
			@SuppressWarnings("unused")
			FeatureBean featureBean = dao.daoGetReferencedByFeatureMd5OnFace(input);
			return FacedbTypeTransformer.CODEINFO_FUN.apply(input);
		}};
	private final Function<Integer,CodeInfo> transFaceId2CodeInfo = Functions.compose(transFace2CodeInfo, dao.daoCastFaceFromPk);

	@Override
	public CodeInfo getCodeInfo(int faceId){
		return transFaceId2CodeInfo.apply(faceId);
	}
	@Override
	public CodeInfo getCodeInfoByFeatureId(String featureId){
		FaceBean faceBean =  getFaceByFeatureId(featureId);
		return getCodeInfo(faceBean.getId());
	}
	@Override
	public List<CodeInfo> getCodeInfosByFeatureId(String featureId){
		List<FaceBean> faceBean =  getFacesByFeatureId(featureId);
		return Lists.transform(faceBean, transFace2CodeInfo);
	}
	@Override
	public List<CodeInfo> getCodeInfosByImageMd5(String imageMd5){
		List<FaceBean> faceBean =  this.getFacesByImageMd5(imageMd5);
		return Lists.transform(faceBean, transFace2CodeInfo);
	}
	@Override
	public CodeInfo getCodeInfoByImageMd5(String imageMd5){
		FaceBean faceBean =  getFaceByImageMd5(imageMd5);
		return transFace2CodeInfo.apply(faceBean);
	}
	@Override
	public FeatureBean getFeature(String featureId) {
		return dao.daoGetFeature(featureId);
	}

	@Override
	public List<FaceBean> getFacesByImageMd5(String imageMd5) {
		return dao.daoGetFaceBeansByImageMd5OnImage(imageMd5);
	}

	@Override
	public List<FeatureBean> getFeaturesByImageMd5(String imageMd5) {
		return dao.daoGetFeaturesByImageMd5(imageMd5);
	}

	@Override
	public List<FaceBean> getFacesByFeatureId(String featureId) {
		return dao.daoGetFaceBeansByFeatureMd5OnFeature(featureId);
	}

	@Override
	public ImageBean getImage(String imageMd5) {
		return dao.daoGetImage(imageMd5);
	}
	@Override
	public ImageBean getImage(String primaryKey,String refType){
		return dao.daoGetImage(primaryKey, 
				RefSrcType.valueOf(MoreObjects.firstNonNull(refType,RefSrcType.DEFAULT.name())));
	}
	@Override
	public List<ImageBean> getImagesByFeatureId(String featureId) {
		return dao.daoGetImagesByFeatureId(featureId);
	}

	@Override
	public byte[] getImageBytes(String imageMd5) {
		return dao.daoGetImageBytes(imageMd5);
	}
	@Override
	public byte[] getImageBytes(String primaryKey,String refType) {
		ImageBean imageBean = getImage(primaryKey,refType);
		return null == imageBean ? null : dao.daoGetImageBytes(imageBean.getMd5());
	}
	@Override
	public int getImageCount(String where) {
		return dao.daoCountImageByWhere(where);
	}

	@Override
	public int getFaceCount(String where) {
		return dao.daoCountFaceByWhere(where);
	}

	@Override
	public int getFeatureCount() {
		return dao.daoCountFeatureByWhere(null);
	}

	@Override
	public List<String> loadImagesMd5ByCreateTime(Date timestamp){
		return dao.daoLoadImageMd5ByCreateTime(timestamp);
	}

	@Override
	public List<String> loadImagesMd5ByCreateTime(String timestamp){
		try{
			return dao.daoLoadImageMd5ByCreateTime(toDate(checkNotNull(timestamp,"timestamp is null")));
		} catch (ParseException e) {
			throw new RuntimeException(e);
		}
	}
	@Override
	public List<String> loadFeaturesMd5ByCreateTime(Date timestamp){
		return dao.daoLoadFeatureMd5ByCreateTime(timestamp);
	}

	@Override
	public List<String> loadFeaturesMd5ByCreateTime(String timestamp){
		try {
			return dao.daoLoadFeatureMd5ByCreateTime(toDate(checkNotNull(timestamp,"timestamp is null")));
		} catch (ParseException e) {
			throw new RuntimeException(e);
		}
	}
	
	@Override
	public List<String> loadImagesMd5ByWhere(String where){
		return dao.daoLoadImageMd5ByWhere(where);
	}
	@Override
	public List<String> loadFeaturesMd5ByWhere(String where){
		return dao.daoLoadFeatureMd5ByWhere(where);
	}
	@Override
	public List<ImageBean> loadImagesByWhere(String where, int startRow, int numRows) {
		return dao.daoLoadImageByWhere(where, startRow, numRows);
	}


	@Override
	public final boolean isLocal() {
		return true;
	}

	@Override
	public Map<String, String> dbCapacity(){
		return roCapacity;
	}
	///////////////// NO IMPLEMENT MEHOTDS ///////////////////
	
	public CodeInfo[] detectAndGetCodeInfo(byte[] imgData) throws ImageErrorException {
		throw new UnsupportedOperationException();
	}

	@Override
	public ImageBean addImageIfAbsent(byte[] imgData, CodeInfo code, double similarty)
			throws NotFaceDetectedException, ImageErrorException {
		throw new UnsupportedOperationException();
	}

	@Override
	public double compareFeature(String featureId, byte[] feature) throws NotFoundBeanException {
		throw new UnsupportedOperationException();
	}

	@Override
	public double compareFeatureId(String featureId1, String featureId2) throws NotFoundBeanException {
		throw new UnsupportedOperationException();

	}

	@Override
	public double[] compareFeatures(String featureId, CodeInfo[] features) throws NotFoundBeanException {
		throw new UnsupportedOperationException();
	}

	@Override
	public double[] compareFaces(String featureId, byte[] imgData, CodeInfo[] facePos)
			throws NotFoundBeanException, NotFaceDetectedException {
		throw new UnsupportedOperationException();
	}

	@Override
	public ImageBean detectAndAddFeatures(byte[] imgData, int faceNum)
			throws DuplicateRecordException, ImageErrorException, NotFaceDetectedException {
		throw new UnsupportedOperationException();
	}

	@Override
	public CompareResult detectAndCompareFaces(String featureId, byte[] imgData, int faceNum)
			throws NotFoundBeanException, ImageErrorException, NotFaceDetectedException {
		throw new UnsupportedOperationException();
	}

	@Override
	public SearchResult[] detectAndSearchFaces(byte[] imgData, double similarty, int rows, String where) throws ImageErrorException, NotFaceDetectedException {
		throw new UnsupportedOperationException();
	}

	@Override
	public SearchResult[] searchFaces(byte[] imgData, CodeInfo facePos, double similarty, int rows, String where)
			throws NotFaceDetectedException, ImageErrorException {
		throw new UnsupportedOperationException();
	}
	
	public SearchResult[] searchFaces(MatType matType, byte[] matData, int width, int height, CodeInfo facePos, double similarty,
			int rows, String where) throws NotFaceDetectedException {
		throw new UnsupportedOperationException();
	}

	@Override
	public SearchResult[] searchFeatures(byte[] feature, double similarty, int rows, String where) {
		throw new UnsupportedOperationException();
	}

	public CodeInfo[] detectAndGetCodeInfo(MatType matType, byte[] matData, int width, int height) throws ImageErrorException {
		return null;
	}

}
